package weeny.recycle;

import java.io.InputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import prefuse.data.Graph;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.io.*;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ParserFactory;
import prefuse.util.collections.IntIterator;
import weeny.pre.io.parser.ColorParser;

/**
 * <p>it extends the GraphMLReader class, 
 * by taking boolean[], int[], float[], long[] and double[] as table fields.</p>
 * 
 * give two readAsSchemas, each colume in the schema should be String -> extends of Collection.
 * And the the saved values for this name field *SHOULD BE* some kind of array: double, int, etc.
 * Then we can read them and save them as an object of Collection.
 * Otherwise, they are just read as arrays
 */
@Deprecated
public class ArraySupportedGraphReader extends AbstractGraphReader  implements GraphReader {
    
    /**
     * @see prefuse.data.io.GraphReader#readGraph(java.io.InputStream)
     */
    public Graph readGraph(InputStream is) throws DataIOException {
        try {       
            SAXParserFactory factory   = SAXParserFactory.newInstance();
            SAXParser        saxParser = factory.newSAXParser();
            
            GraphMLHandler   handler   = new GraphMLHandler(readNodesAs, readEdgesAs);
            saxParser.parse(is, handler);
            return handler.getGraph();
        } catch ( Exception e ) {
            if ( e instanceof DataIOException ) {
                throw (DataIOException)e;
            } else {
                throw new DataIOException(e);
            }
        }
    }
    public ArraySupportedGraphReader() {}
    public ArraySupportedGraphReader(Schema replaceNodeSchema){
    	readNodesAs = replaceNodeSchema;
    }
    public ArraySupportedGraphReader(Schema replaceNodeSchema, Schema replaceEdgeSchema) {
    	readNodesAs = replaceNodeSchema;
    	readEdgesAs = replaceEdgeSchema;
	}
    
	public static List<String> csvStringList(String line){
		List<String> result = new ArrayList<String>();
        boolean inRecord = false;
        int inQuote  = 0;
        int lineno   = 0;
        int col      = 0;
        StringBuffer sbuf = new StringBuffer();
        
		char[] c = line.toCharArray();
        int last = c.length-1;
        
        // iterate through current line
        for ( int i=0; i<=last; ++i ) {
            if ( !inRecord ) {
                // not currently processing a record
                if ( Character.isWhitespace(c[i]) )
                {
                    continue;
                }
                else if ( c[i] == '\"' )
                {
                    inRecord = true;
                    inQuote  = 1;
                }
                else if ( c[i] == ',' )
                {
//                    String s = sbuf.toString().trim();
                	String s= sbuf.toString();
                    result.add(s);
                    sbuf.delete(0, sbuf.length());
                }
                else
                {
                    inRecord = true;
                    sbuf.append(c[i]);
                }
            } else {
                // in the midst of a record
                if ( inQuote == 1 ) {
                    if ( c[i]=='\"' && (i==last || c[i+1] != '\"') )
                    {
                        // end of quotation
                        inQuote = 2;
                    }
                    else if ( c[i]=='\"' )
                    {
                        // double quote so skip one ahead
                        sbuf.append(c[i++]);
                    }
                    else
                    {
                        sbuf.append(c[i]);
                    }
                } else {
                    if ( Character.isWhitespace(c[i]) )
                    {
                        sbuf.append(c[i]);
                    }
                    else if ( c[i] != ',' && inQuote == 2 )
                    {
                        throw new IllegalStateException(
                            "Invalid data format. " + 
                            "Error at line " + lineno + ", col " + i);
                    }
                    else if ( c[i] != ',' )
                    {
                        sbuf.append(c[i]);
                    }
                    else
                    {
//                        String s = sbuf.toString().trim();
                    	String s= sbuf.toString();
                        result.add(s);
                        sbuf.delete(0, sbuf.length());
                        inQuote = 0;
                        inRecord = false;
                    }
                }
            }
        }
        if ( inQuote != 1 ) {
//            String s = sbuf.toString().trim();
        	String s= sbuf.toString();
            result.add(s);
            sbuf.delete(0, sbuf.length());
            inQuote = 0;
            inRecord = false;
        }
        if ( !inRecord && col > 0 ) {
            col = 0;
        }
        return result;
	}

    
    protected Schema readNodesAs = null;
    protected Schema readEdgesAs = null;
    
    /**
     * String tokens used in the GraphML format.
     */
    public interface Tokens extends GraphMLReader.Tokens{
        public static final String INT_ARRAY = "intarray";
        public static final String FLOAT_ARRAY = "floatarray";
        public static final String DOUBLE_ARRAY = "doublearray";
        public static final String BOOLEAN_ARRAY = "booleanarray";
        public static final String LONG_ARRAY = "longarray";
        public static final String STRING_ARRAY = "stringarray";
    }
    
    /**
     * A SAX Parser for GraphML data files.
     */
    public static class GraphMLHandler extends DefaultHandler implements Tokens
    {
    	protected Schema readNodesAs = null;
    	protected Schema readEdgesAs = null;
    	public GraphMLHandler(Schema nodeSchema, Schema edgeSchema) {
    		readNodesAs = nodeSchema;
    		readEdgesAs = edgeSchema;
		}
        protected static ParserFactory m_pf = ParserFactory.getDefaultFactory();
        
        protected static final String SRC = Graph.DEFAULT_SOURCE_KEY;
        protected static final String TRG = Graph.DEFAULT_TARGET_KEY;
        protected static final String SRCID = SRC+'_'+ID;
        protected static final String TRGID = TRG+'_'+ID;
        
        protected Schema m_nsch = new Schema();
        protected Schema m_esch = new Schema();
        
        protected String m_graphid;
        protected Graph m_graph = null;
        protected Table m_nodes;
        protected Table m_edges;
        
        // schema parsing
        protected String m_id;
        protected String m_for;
        protected String m_name;
        protected String m_type;
        protected String m_dflt;
        
        protected StringBuffer m_sbuf = new StringBuffer();
        
        // node,edge,data parsing
        private String m_key;
        private int m_row = -1;
        private Table m_table = null;
        protected HashMap m_nodeMap = new HashMap();
        protected HashMap m_idMap = new HashMap();
        
        private boolean m_directed = false;
        private boolean inSchema;
        
        public void startDocument() {
            m_nodeMap.clear();
            inSchema = true;
            
            m_esch.addColumn(SRC, int.class);
            m_esch.addColumn(TRG, int.class);
            m_esch.addColumn(SRCID, String.class);
            m_esch.addColumn(TRGID, String.class);
        }
        
        public void endDocument() throws SAXException {
            // time to actually set up the edges
            IntIterator rows = m_edges.rows();
            while (rows.hasNext()) {
                int r = rows.nextInt();

                String src = m_edges.getString(r, SRCID);
                if (!m_nodeMap.containsKey(src)) {
                    throw new SAXException(
                        "Tried to create edge with source node id=" + src
                        + " which does not exist.");
                }
                int s = ((Integer) m_nodeMap.get(src)).intValue();
                m_edges.setInt(r, SRC, s);

                String trg = m_edges.getString(r, TRGID);
                if (!m_nodeMap.containsKey(trg)) {
                    throw new SAXException(
                        "Tried to create edge with target node id=" + trg
                        + " which does not exist.");
                }
                int t = ((Integer) m_nodeMap.get(trg)).intValue();
                m_edges.setInt(r, TRG, t);
            }
            m_edges.removeColumn(SRCID);
            m_edges.removeColumn(TRGID);

            // now create the graph
            m_graph = new Graph(m_nodes, m_edges, m_directed);
            if (m_graphid != null)
                m_graph.putClientProperty(ID, m_graphid);
        }
        
        public void startElement(String namespaceURI, String localName, 
                                 String qName, Attributes atts)
        {
            // first clear the character buffer
            m_sbuf.delete(0, m_sbuf.length());
            
            if ( qName.equals(GRAPH) )
            {
                // parse directedness default
                String edef = atts.getValue(EDGEDEF);
                m_directed = DIRECTED.equalsIgnoreCase(edef);
                m_graphid = atts.getValue(ID);
            }
            else if ( qName.equals(KEY) )
            {
                if ( !inSchema ) {
                    error("\""+KEY+"\" elements can not"
                        + " occur after the first node or edge declaration.");
                }
                m_for = atts.getValue(FOR);
                m_id = atts.getValue(ID);
                m_name = atts.getValue(ATTRNAME);
                m_type = atts.getValue(ATTRTYPE);
            }
            else if ( qName.equals(NODE) )
            {
                schemaCheck();
                
                m_row = m_nodes.addRow();
                
                String id = atts.getValue(ID);
                m_nodeMap.put(id, new Integer(m_row));
                m_table = m_nodes;
            }
            else if ( qName.equals(EDGE) )
            {
                schemaCheck();
                
                m_row = m_edges.addRow();
                
                // do not use the id value
//                String id = atts.getValue(ID);
//                if ( id != null ) {
//                    if ( !m_edges.canGetString(ID) )
//                        m_edges.addColumn(ID, String.class);
//                    m_edges.setString(m_row, ID, id);
//                }
                m_edges.setString(m_row, SRCID, atts.getValue(SRC));
                m_edges.setString(m_row, TRGID, atts.getValue(TRG));
                
                // currently only global directedness is used
                // ignore directed edge value for now
//                String dir = atts.getValue(DIRECTED);
//                boolean d = m_directed;
//                if ( dir != null ) {
//                    d = dir.equalsIgnoreCase("false");
//                }
//                m_edges.setBoolean(m_row, DIRECTED, d);
                m_table = m_edges;
            }
            else if ( qName.equals(DATA) )
            {
                m_key = atts.getValue(KEY);
            }
        }

        public void endElement(String namespaceURI, 
                String localName, String qName)
        {
            if ( qName.equals(DEFAULT) ) {
                // value is in the buffer
                m_dflt = m_sbuf.toString();
            }
            else if ( qName.equals(KEY) ) {
                // time to add to the proper schema(s)
                addToSchema();
            }
            else if ( qName.equals(DATA) ) {
                // value is in the buffer
                String value = m_sbuf.toString();
                String name = (String)m_idMap.get(m_key);
                Class type = m_table.getColumnType(name);
                try {
                    Object val = getValue(name, value, type);
                    m_table.set(m_row, name, val);
                } catch ( DataParseException dpe ) {
                    error(dpe);
                }
            }
            else if ( qName.equals(NODE) || qName.equals(EDGE) ) {
                m_row = -1;
                m_table = null;
            }
        }
        protected Object getValue(String name, String value, Class tableType) throws DataParseException{
        	if (value.equals("null")) {
				return null;
			}
        	if (!name2OldType.containsKey(name)) {
				return parse(value, tableType);
			}
        	else{
        		//tableType here is the target type we want to store those arrays
        		Object val = parse(value, name2OldType.get(name));
        		Collection obj;
				try {
					obj = (Collection) tableType.newInstance();
					for(int i = 0; i<Array.getLength(val); i++){
						obj.add(Array.get(val, i));
					}
	        		return obj;
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
        	}
        	return null;
        }
        
        public void characters(char[] ch, int start, int length) throws SAXException {
            m_sbuf.append(ch, start, length);
        }

        // --------------------------------------------------------------------
        
        protected void schemaCheck() {
            if ( inSchema ) {
                m_nsch.lockSchema();
                m_esch.lockSchema();
                m_nodes = m_nsch.instantiate();
                m_edges = m_esch.instantiate();
                inSchema = false;
            }
        }
        
        protected Map<String, Class> name2OldType = new HashMap<String, Class>();  
        protected void addToSchema() {
            if ( m_name == null || m_name.length() == 0 )
                error("Empty "+KEY+" name.");
            if ( m_type == null || m_type.length() == 0 )
                error("Empty "+KEY+" type.");
            
            try {
                Class typeInFile = parseType(m_type);
                Object dflt = m_dflt==null ? null : parse(m_dflt, typeInFile);
                if ( m_for == null || m_for.equals(ALL) || m_for.equals(NODE) ) {
                	if (readNodesAs != null && readNodesAs.getColumnType(m_name) != null) {
                		if (Collection.class.isAssignableFrom(readNodesAs.getColumnType(m_name))) {
                    		name2OldType.put(m_name, typeInFile);
                    		typeInFile = readNodesAs.getColumnType(m_name);
						}
					}
                    m_nsch.addColumn(m_name, typeInFile, dflt);
                } 
                if ( m_for == null || m_for.equals(ALL) || m_for.equals(EDGE) ) {
                	if (readEdgesAs != null && readEdgesAs.getColumnType(m_name) != null) {
                		if (Collection.class.isAssignableFrom(readEdgesAs.getColumnType(m_name))) {
                    		name2OldType.put(m_name, typeInFile);
                    		typeInFile = readEdgesAs.getColumnType(m_name);
						}
					}
                    m_esch.addColumn(m_name, typeInFile, dflt);
                } 
                else if ( m_for != null && !m_for.equals(ALL) && !m_for.equals(EDGE) && !m_for.equals(NODE)){
                    error("Unrecognized \""+FOR+"\" value: "+ m_for);
                }
                m_idMap.put(m_id, m_name);
                
                m_dflt = null;
            } catch ( DataParseException dpe ) {
                error(dpe);
            }
        }
        
        protected Class parseType(String type) {
            type = type.toLowerCase();
            if ( type.equals(INT) || type.equals(INTEGER) ) {
                return int.class;
            } else if ( type.equals(LONG) ) {
                return long.class;
            } else if ( type.equals(FLOAT) ) {
                return float.class;
            } else if ( type.equals(DOUBLE) || type.equals(REAL)) {
                return double.class;
            } else if ( type.equals(BOOLEAN) ) {
                return boolean.class;
            } else if ( type.equals(STRING) ) {
                return String.class;
            } else if ( type.equals(DATE) ) {
                return Date.class;
            } else if ( type.equals(INT_ARRAY) ) {
                return int[].class;
            } else if ( type.equals(FLOAT_ARRAY) ) {
                return float[].class;
            } else if ( type.equals(DOUBLE_ARRAY) ) {
                return double[].class;
            } else if ( type.equals(BOOLEAN_ARRAY)) {
            	return boolean[].class;
            } else if ( type.equals(LONG_ARRAY)) {
            	return long[].class;
            } else if ( type.equals(STRING_ARRAY)){
            	return String[].class;
            } else if ( type.equals(DATE) ) {
                return Date.class;
            } else {
                error("Unrecognized data type: "+type);
                return null;
            }
        }
        
        protected static Object parse(String s, Class type)
            throws DataParseException
        {
        	if (s.equals("null")) {
				return null;
			}
        	if (type == String[].class) {
        		if (s.length() == 0) {
					return new String[0];
				}
				List<String> list = csvStringList(s);
				return list.toArray(new String[0]);
			}
        	else if(type == boolean[].class){
        		if (s.length() == 0) {
					return new boolean[0];
				}
        		else{
        			String[] tokens = s.split(" ");
        			boolean[] result = new boolean[tokens.length];
        			for(int i = 0; i<tokens.length; i++){
        				result[i] = Boolean.parseBoolean(tokens[i]);
        			}
        			return result;
        		}
        	}
        	else if(type == int[].class){
        		if (s.length() == 0) {
					return new int[0];
				}
        		else{
        			String[] tokens = s.split(" ");
        			int[] result = new int[tokens.length];
        			for(int i = 0; i<tokens.length; i++){
        				result[i] = Integer.parseInt(tokens[i]);
        			}
        			return result;
        		}
        	}
        	else if(type == float[].class){
        		if (s.length() == 0) {
					return new float[0];
				}
        		else{
        			String[] tokens = s.split(" ");
        			float[] result = new float[tokens.length];
        			for(int i = 0; i<tokens.length; i++){
        				if (tokens[i].startsWith("n")) {
        					result[i] = Float.NaN;
						}
        				else{
        					result[i] = Float.parseFloat(tokens[i]);
        				}
        			}
        			return result;
        		}
        	}
        	else if(type == double[].class){
        		if (s.length() == 0) {
					return new double[0];
				}
        		else{
        			String[] tokens = s.split(" ");
        			double[] result = new double[tokens.length];
        			for(int i = 0; i<tokens.length; i++){
        				if(tokens[i].startsWith("n")){
        					result[i] = Double.NaN;
        				}
        				else{
        					result[i] = Double.parseDouble(tokens[i]);
        				}
        			}
        			return result;
        		}
        	}
        	else{
        		DataParser dp = m_pf.getParser(type);
        		if(type == int.class && s.startsWith("["))
        			return colorParser.parse(s);
        		return dp.parse(s);
        	}
        }
        private static ColorParser colorParser = new ColorParser();
        public Graph getGraph() {
            return m_graph;
        }
        
        protected void error(String s) {
            throw new RuntimeException(s);
        }
        
        protected void error(Exception e) {
            throw new RuntimeException(e);
        }
        
    } // end of inner class GraphMLHandler

} // end of class XMLGraphReader
